package svgedit.gui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.util.ArrayList;

import javax.swing.JComponent;

import svgedit.gui.actions.SelectedElements;
import svgedit.gui.actions.ViewListener;
import svgedit.svg.SVGDocument;
import svgedit.svg.SVGElement;
import svgedit.svg.SVGGroup;
import svgedit.svg.SVGLength;
import svgedit.svg.SVGPaint;
import svgedit.svg.SVGStylable;
import svgedit.svg.SVGViewport;

/** A view onto an {@link SVGDocument}. */

public class View extends JComponent implements SVGViewport
{

	private static final long serialVersionUID = -6357815521551606515L;

	// list of all displayed markers' listeners
	private ArrayList<Marker> markers;

	// Selected elements in view
	private SelectedElements selectedElements;

	private SVGDocument document;
	private ViewListener viewListener;

	// The selected rectangle
	private Rectangle selectRect;
	// Whether to show selection or not
	private boolean showSelection;

	/** Creates a new view */
	public View(Toolbar toolbar)
	{

		this.viewListener = new ViewListener(this, toolbar);
		// Add listener
		addMouseListener(this.viewListener);
		addMouseMotionListener(this.viewListener);

		// Init markers and selectelements
		markers = new ArrayList<Marker>();
		selectedElements = new SelectedElements(this);

	}

	/**
	 * Get the view listener assigned to the viewport
	 * 
	 * @return ViewListener assigned
	 */
	public ViewListener getViewListener()
	{
		return viewListener;
	}

	/**
	 * Get viewport width
	 */
	public double getViewportWidth()
	{
		return getWidth();
	}

	/**
	 * Get viewport Height
	 */
	public double getViewportHeight()
	{
		return getHeight();
	}

	/**
	 * Internal paint entry point. All drawing in the view uses a
	 * {@link Graphics2D}, so for convenience this method simply delegates to
	 * {@link #paint2D}.
	 * 
	 * @param g
	 *            the {@link Graphics} context to paint to
	 */
	@Override
	protected void paintComponent(Graphics g)
	{

		// Clear mouse listeners
		markers.clear();

		paint2D((Graphics2D) g);

		if (selectRect != null || selectedElements.size() > 0)
			paintSelectionBox(g);

	}

	/**
	 * Paints the entire view.
	 * 
	 * @param g
	 *            the {@link Graphics2D} context to paint to
	 */
	private void paint2D(Graphics2D g)
	{
		// Paint view background
		g.setColor(Color.GRAY);
		g.fillRect(0, 0, getWidth(), getHeight());

		// Paint document background and border
		int documentWidth = (int) document.getWidth().getValue();
		int documentHeight = (int) document.getHeight().getValue();
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, documentWidth, documentHeight);
		g.setColor(Color.BLACK);
		g.drawRect(-1, -1, documentWidth, documentHeight);

		// Paint document, Elements first so they don't override group markers
		for (SVGElement elem : document)
		{
			if (!(elem instanceof SVGGroup))
				paintElement(g, elem);
		}

		// Paint document, Groups last so the markers appear on top
		for (SVGElement elem : document)
		{
			if (elem instanceof SVGGroup)
				paintElement(g, elem);
		}

		g.setColor(Color.GRAY);
		// Bottom-Right
		g.fillRect(documentWidth, documentHeight, this.getWidth()
				- documentWidth, this.getHeight() - documentHeight);

		// Top-Right
		g.fillRect(documentWidth, 0, this.getWidth() - documentWidth,
				documentHeight);

		// Bottom-Left
		g.fillRect(0, documentHeight, documentWidth, this.getHeight()
				- documentHeight);

	}

	/**
	 * Draws the selected rect into the view panel
	 */
	private void paintSelectionBox(Graphics g)
	{
		g.setColor(Color.BLUE);
		// Should the selection be shown?
		if (showSelection == false)
			return;

		Graphics2D g2 = (Graphics2D) g;

		// Turn the selection rect into a shape
		Shape shape = selectRect;

		// Apply the selection stroke to it
		g.setColor(Color.BLUE);
		g2.setStroke(new SelectionRectStroke());
		g2.draw(shape);

	}

	/**
	 * Paints a single element on the graphics context.
	 * 
	 * @param g
	 *            the graphics context to paint to
	 * @param element
	 *            the element to paint
	 */
	public void paintElement(Graphics2D g, SVGElement element)
	{

		Shape shape = element.createShape();

		if ((element instanceof SVGStylable))
		{

			// Shape shape = element.createShape();

			SVGStylable style = (SVGStylable) element;
			SVGPaint fillPaint = style.getFill();
			SVGPaint strokePaint = style.getStroke();
			SVGLength strokeWidth = style.getStrokeWidth();

			// Fill the interior of the shape
			if (fillPaint.getPaintType() == SVGPaint.SVG_PAINTTYPE_RGBCOLOR)
			{
				g.setPaint(fillPaint.getRGBColor());
				g.fill(shape);
			}

			// Stroke the outline of the shape
			if (strokePaint.getPaintType() == SVGPaint.SVG_PAINTTYPE_RGBCOLOR)
			{
				Stroke stroke = new BasicStroke((float) strokeWidth.getValue());
				g.setStroke(stroke);
				g.setColor(strokePaint.getRGBColor());
				g.draw(shape);
			}

		}

		// Dont draw outline if in a group
		if (getSelected().contains(element.getParent()))
			return;

		// Selection box
		if (getSelected().size() == 0)
			return;

		// Is the element selected
		if (getSelected().contains(element))
		{
			// Shows the vertices & control points
			ControlPointsStroke controlStroke = new ControlPointsStroke(5.0f,
					element);

			// create stroke runs when u instantiate above
			// selectionStroke.createStrokedShape(shape);
			g.setStroke(controlStroke);
			g.setColor(Color.BLUE);
			g.draw(shape);

			Frame.getFrame().getView().getMarkers()
					.addAll(element.getMarkers());

		}

	}

	/** Gets the document currently being displayed by the view. */
	public SVGDocument getDocument()
	{
		return document;
	}

	/**
	 * Return the root group
	 * 
	 * @return Return the root group
	 */
	public SVGGroup getRootGroup()
	{
		return this.document.getRootGroup();
	}

	/**
	 * Sets the document that the view should display.
	 * 
	 * @param document
	 *            the document to set
	 */
	public void setDocument(SVGDocument document)
	{
		this.document = document;
		repaint();
	}

	/**
	 * @return the selectRect
	 */
	public Rectangle getSelectRect()
	{
		return selectRect;
	}

	/**
	 * Used for when mouse released, we want to retain selection but not show
	 * the selected outline
	 * 
	 * @param selectRect
	 *            The selection rect Rectangle/Bounds
	 * @param showSelection
	 *            Whether to show or not, sometimes for (clicking, we dont want
	 *            to but still want to parse rect, dragging yes tho)
	 */
	public void setSelectRect(Rectangle selectRect, boolean showSelection)
	{
		this.selectRect = selectRect;
		this.showSelection = showSelection;

		// Clear selected elements
		selectedElements.clear();

		// If no selection rect
		if (selectRect != null)
		{
			for (SVGElement element : document)
				if (selectRect.intersects(element.getBounds()))
					// selectedElements.add(element);
					selectedElements.add(element);
		}

		// Does the selection include groups, then show/hide menuitem in
		// menubar
		if (getSelected().includesGroups())
			Frame.getFrame().enableUnGroupMenuItem(true);
		else
			Frame.getFrame().enableUnGroupMenuItem(false);

		// Show hide/menu item in menubar
		if (getSelected().size() >= 2)
			Frame.getFrame().enableGroupMenuItem(true);
		else
			Frame.getFrame().enableGroupMenuItem(false);

		// Show hide/menu item in menubar
		if (getSelected().size() > 0)
			Frame.getFrame().enableDeleteMenuItem(true);
		else
			Frame.getFrame().enableDeleteMenuItem(false);

	}

	/**
	 * Add element to selected elements
	 * 
	 * @param e
	 *            The element to add
	 */
	public void insertIntoSelection(SVGElement e)
	{
		this.selectedElements.add(e);
	}

	/**
	 * Get the elements currently selected
	 * 
	 * @return SelectedElements
	 */
	public SelectedElements getSelected()
	{
		return this.selectedElements;
	}

	/**
	 * Return current markers in Viewport
	 * 
	 * @return Array of Marker type
	 */
	public ArrayList<Marker> getMarkers()
	{
		return markers;
	}

}